package cn.thoughtworks.school.controllers;

import cn.thoughtworks.school.configrations.TokenUtil;
import cn.thoughtworks.school.entities.CreateUserCommand;
import cn.thoughtworks.school.entities.Pair;
import cn.thoughtworks.school.entities.SocialAccount;
import cn.thoughtworks.school.entities.User;
import cn.thoughtworks.school.exceptions.BusinessException;
import cn.thoughtworks.school.services.ProgramService;
import cn.thoughtworks.school.services.UserService;
import cn.thoughtworks.school.utils.WechatUtil;
import cn.thoughtworks.school.utils.captcha.CaptchaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

@Controller
@RequestMapping(value = "/api/auth")
public class AuthController {


  @Autowired
  private UserService userService;
  @Autowired
  private ProgramService programService;
  @Value("${appContextPath}")
  private String appContextPath;
  @Autowired
  private CaptchaService captchaService;

  @PostMapping("username")
  public ResponseEntity usernameLogin(@RequestBody User user,
                                      @RequestParam(required = false) String code) throws UnsupportedEncodingException, BusinessException {
    Map result = new HashMap<>();

    User current = userService.usernameLogin(user);
    String jwt = userService.getJwt(current);
    result.put("jwt", jwt);
    result.put("sessionId", UUID.randomUUID().toString());
    result.put("userId", current.getId());

    programService.dealProgram(code, current.getId());
    return ResponseEntity.ok(result);
  }

  @GetMapping("QRCode/{env}")
  public String loginScanQRCode(@PathVariable String env) {
    String requestCode = WechatUtil.getLoginRequestCodeUrl(env);

    return "redirect:" + requestCode;
  }

  @PostMapping("bindQRCode/{username}")
  public String bindingScanQRCode(@PathVariable String username) {
    String requestCode = WechatUtil.getWechatBindingRedirectUrl(username);

    return "redirect:" + requestCode;
  }

  @GetMapping("redirection/{env}")
  public String redirect(@RequestParam String code, @PathVariable String env) {
    String context = "staging".equals(env) ? "staging." : "";
    return "redirect:" + String.format("http://%sschool.thoughtworks.cn/learn/auth/redirect?code=%s", context, code);
  }

  @GetMapping("wechatBind/{username}")
  public String wechatBind(@RequestParam String code, @PathVariable String username) throws IOException, BusinessException {
    SocialAccount wechatInfo = WechatUtil.getSocialAccount(code);
    SocialAccount socialAccount = userService.findSocialAccountByOpenid(wechatInfo.getOpenid());

    if (isBinding(socialAccount)) {
      throw new BusinessException("current wechat has already bind another account");
    }

    userService.bindWechat(username, wechatInfo);

    String context = "/learn".equals(appContextPath) ? "" : "staging.";
    return "redirect:" + String.format("http://%sschool.thoughtworks.cn/learn/home/index.html", context);
  }


  @GetMapping("wechat")
  public ResponseEntity wechatLogin(@RequestParam String code) throws IOException {
    Map result = new HashMap();
    SocialAccount wechatInfo = WechatUtil.getSocialAccount(code);
    SocialAccount socialAccount = userService.findSocialAccountByOpenid(wechatInfo.getOpenid());

    if (isBinding(socialAccount)) {
      String jwt = userService.wechatLogin(socialAccount);
      result.put("jwt", jwt);
      result.put("sessionId", UUID.randomUUID().toString());
    }

    if (userService.nonExistSocialAccount(socialAccount)) {
      socialAccount = userService.saveSocialAccount(wechatInfo);
    }

    result.put("id", socialAccount.getId());

    return ResponseEntity.ok(result);
  }

  @GetMapping("socialAccount/{id}")
  public ResponseEntity<SocialAccount> getSocialAccount(@PathVariable Long id) throws BusinessException {
    SocialAccount account = userService.findSocialAccountById(id);

    return ResponseEntity.ok(account);
  }

  @PostMapping("users/register")
  ResponseEntity register(@RequestBody CreateUserCommand userCommand) throws Exception {
    validateUser(userCommand);
    User createdUser = userService.createUser(userCommand);

    if (isBindWechat(userCommand)) {
      userService.dealBindSocialAccount(createdUser, userCommand.getSocialAccountId());
    }
    programService.dealProgram(userCommand.getCode(), createdUser.getId());

    Map result = new HashMap();
    String jwt = userService.getJwt(createdUser);
    result.put("userId", createdUser.getId());
    result.put("jwt", jwt);
    result.put("sessionId", UUID.randomUUID().toString());
    return new ResponseEntity(result, HttpStatus.CREATED);
  }

  @PostMapping("users/joinProgram")
  public ResponseEntity userJoinProgram(HttpServletRequest request, @RequestBody CreateUserCommand userCommand) throws Exception {
    String token = getTokenHeader(request);
    User user = TokenUtil.parseTokenToJsonObject(token);
    programService.dealProgram(userCommand.getCode(), user.getId());
    return new ResponseEntity(HttpStatus.CREATED);
  }
  @PostMapping("users/joinProgram/userId/{id}")
  public ResponseEntity userJoinProgramByUserId(@PathVariable Long id, @RequestBody CreateUserCommand userCommand) throws Exception {
      programService.dealProgram(userCommand.getCode(), id);
      return new ResponseEntity(HttpStatus.CREATED);
  }

  private String getTokenHeader(HttpServletRequest request) {
    return request.getHeader("token");
  }

  private boolean isBindWechat(CreateUserCommand userCommand) {
    return Objects.nonNull(userCommand.getSocialAccountId());
  }

  private void validateUser(CreateUserCommand userCommand) throws BusinessException {
    User user = userCommand.buildUser();
    userService.validDateUser(user);
    userService.validParams(userCommand);
  }

  private boolean isBinding(SocialAccount socialAccount) {
    return Objects.nonNull(socialAccount) && Objects.nonNull(socialAccount.getUsername());
  }

  @RequestMapping(method = RequestMethod.GET, path = "/captcha/{captchaId}")
  public void captcha(HttpServletResponse response, @PathVariable("captchaId") String captchaId) throws IOException {
    try {
      BufferedImage captchaImage = captchaService.getImageChallengeForID(captchaId);

      response.setContentType("image/jpeg");
      ImageIO.write(captchaImage, "jpg", response.getOutputStream());
    } catch (Exception e) {
      throw e;
    }
  }

  @RequestMapping(method = RequestMethod.POST, path = "/verifyCaptcha")
  public ResponseEntity verifyCaptcha(@RequestBody Pair captcha) {
    boolean status = captchaService.isValidResponse(captcha.getKey(), captcha.getValue().toUpperCase());
    return ResponseEntity.ok(status);
  }

}
